﻿using System.Collections.Generic;
using Util.QueryParser.Token;

namespace Util.QueryParser
{
    public sealed class Parser
    {
        private Parser() { }

        #region Parser

        public static IToken BuildTokenTree(string query)
        {
            lex("( " + query + " )");

/*            IToken t = parseToken();
            do
            {
                if (peekNextItem() == "AND")
                {
                    t = parseAnd(t);
                }
                else if (peekNextItem() == "OR")
                {
                    t = parseOr(t);
                }
                else if (peekNextItem() != null)
                {
                    throw new ParserException();
                }
            } while (peekNextItem() != null);

            return t;*/
            return parseToken();
        }


        private static BooleanOr parseOr(IToken t)
        {
            getNextItem();
            IToken t2 = parseToken();
            if (peekNextItem() == "AND")
            {
                return new BooleanOr(t, parseAnd(t2));
            }
            else
            {
                return new BooleanOr(t, t2);
            }
        }

        private static BooleanAnd parseAnd(IToken t)
        {
            getNextItem();
            return new BooleanAnd(t, parseToken());
        }


        private static IToken parseToken()
        {
            string nextToken = getNextItem();
            if (nextToken == "NOT")
            {
                return new BooleanNot(parseToken());
            }
            if (nextToken[0] == '\"')
            {
                return new LiteralCI(nextToken);
            }
            else if (nextToken[0] == '\'')
            {
                return new Literal(nextToken);
            }
            else if (nextToken == "(")
            {
                IToken t = parseToken();
                do
                {
                    if (peekNextItem() == "AND")
                    {
                        t = parseAnd(t);
                    }
                    else if (peekNextItem() == "OR")
                    {
                        t = parseOr(t);
                    }
                    else if (peekNextItem() != ")")
                    {
                        throw new ParserException();
                    }
                } while (peekNextItem() != ")");
                getNextItem();
                return t;
            }

            throw new ParserException();
        }

        #endregion

        #region Lexer

        private static string[] lex_data;
        private static int lex_pos;

        private static void lex(string qry)
        {
            List<string> lst = new List<string>();
            int i = 0;
            while (i < qry.Length)
            {
                if (char.IsWhiteSpace(qry[i]))
                {
                    ++i;
                }
                else if (qry[i] == '(')
                {
                    lst.Add("(");
                    ++i;
                }
                else if (qry[i] == ')')
                {
                    lst.Add(")");
                    ++i;
                }
                else if (qry.Substring(i, 3).ToUpper(System.Globalization.CultureInfo.InvariantCulture) == "AND")
                {
                    lst.Add("AND");
                    i += 3;
                }
                else if (qry.Substring(i, 2).ToUpper(System.Globalization.CultureInfo.InvariantCulture) == "OR")
                {
                    lst.Add("OR");
                    i += 2;
                }
                else if (qry.Substring(i, 3).ToUpper(System.Globalization.CultureInfo.InvariantCulture) == "NOT")
                {
                    lst.Add("NOT");
                    i += 3;
                }
                else if (qry[i] == '\"' || qry[i] == '\'')
                {
                    char stopchar = qry[i];
                    int n = i + 1;
                    while (qry[n] != stopchar)
                    {
                        if (qry[n] == '\\')
                        {
                            n++;
                        }
                        n++;
                    }
                    lst.Add(qry.Substring(i, n - i + 1));
                    i = n + 1;
                }
                else
                {
                    throw new ParserException();
                }
            }
            lex_data = lst.ToArray();
            lex_pos = 0;
        }


        private static string getNextItem()
        {
            if (lex_pos == lex_data.Length)
            {
                return null;
            }
            lex_pos++;
            return lex_data[lex_pos - 1];
        }

        private static string peekNextItem()
        {
            if (lex_pos == lex_data.Length)
            {
                return null;
            }
            return lex_data[lex_pos];
        }

        #endregion
    }
}
